package com.ncf.jucai.accessmeter;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.input.Tailer;
import org.apache.commons.io.input.TailerListener;

import java.io.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 分析AccessLog Created by lichengwen on 7/11/16.
 */
@Slf4j
class AccessLogAnalyzer
{
    private static int mostCostCount = Integer.parseInt(Config.get("count.mostCost", "10"));

    /**
     * key：毫秒数，value：个数
     */
    static final Map<Integer, Counter> costCounters = new TreeMap<>(new Comparator<Integer>()
    {
        @Override
        public int compare(Integer o1, Integer o2)
        {
            if (o1 < o2) {
                return 1;
            } else if (o1 > o2) {
                return -1;
            } else {
                return 0;
            }
        }
    });

    static final            LinkedList<AccessLog> mostCostLogs = new LinkedList<>();
    private static volatile int                   minCost      = -1;

    static void go() throws Exception
    {
        new Timer().schedule(new TimerTask()
        {
            Tailer tailer;

            @Override
            public void run()
            {
                File todayTarget = new File(getTargetPath());
                if (tailer == null && todayTarget.exists()) {
                    log.info("use analyze log target:{}", todayTarget);
                    tailer = startTailer(todayTarget);
                }
                if (tailer != null && !todayTarget.equals(tailer.getFile())) {
                    tailer.stop();
                    tailer = null;
                }
            }
        }, 1000, 1000);
    }

    // %h %l %u %t &quot;%r&quot; %s %D %b
    private static Pattern LOG_PTN = Pattern.compile("(?<ip>[0-9a-f\\.:]{8,39}) - - " +
                                                     "\\[(?<time>.*?)] " +
                                                     "\"" +
                                                     "(?<method>[A-Z]+) " +
                                                     "(?<path>\\S+)" +
                                                     "(?<query>\\?\\S*)? " +
                                                     "(?<protocol>\\S+)" +
                                                     "\" " +
                                                     "(?<status>\\d+) " +
                                                     "(?<cost>\\d+) " +
                                                     "(?<bytes>\\d+|-)");

    private static Tailer startTailer(File target)
    {
        final Tailer tailer = new Tailer(target, new TailerListener()
        {
            @Override
            public void init(Tailer tailer) {}

            @Override
            public void fileNotFound() {}

            @Override
            public void fileRotated() {}

            @Override
            public void handle(String line)
            {
                Matcher m = LOG_PTN.matcher(line);
                if (m.find()) {
                    AccessLog al = new AccessLog();
                    al.setIp(m.group("ip"));
                    al.setTime(m.group("time"));
                    al.setMethod(m.group("method"));
                    al.setPath(m.group("path"));
                    al.setQuery(m.group("query"));
                    al.setProtocol(m.group("protocol"));
                    String s = m.group("status");
                    if (s.matches("\\d+")) {
                        al.setStatus(Integer.valueOf(s));
                    }
                    s = m.group("cost");
                    if (s.matches("\\d+")) {
                        al.setCost(Integer.valueOf(s));
                    }
                    s = m.group("bytes");
                    if (s.matches("\\d+")) {
                        al.setBytes(Integer.valueOf(s));
                    }
                    al.setLine(line);
                    eat(al);
                } else {
                    log.warn("wrong format log line:" + line);
                }
            }

            @Override
            public void handle(Exception ex) {}
        });
        new Thread(tailer).start();
        return tailer;
    }

    private static void eat(AccessLog al)
    {
        synchronized (mostCostLogs) {
            if (al.getCost() > minCost || mostCostLogs.size() < mostCostCount) {
                mostCostLogs.add(al);
                Collections.sort(mostCostLogs, new Comparator<AccessLog>()
                {
                    @Override
                    public int compare(AccessLog o1, AccessLog o2)
                    {
                        if (o1.getCost() > o2.getCost()) {
                            return -1;
                        } else if (o1.getCost() < o2.getCost()) {
                            return 1;
                        } else {
                            return 0;
                        }
                    }
                });
                if (mostCostLogs.size() > mostCostCount) {
                    mostCostLogs.pollLast();
                }
                minCost = mostCostLogs.getLast().getCost();
            }
        }

        synchronized (costCounters) {
            if (costCounters.containsKey(al.getCost())) {
                costCounters.get(al.getCost()).increase();
            } else {
                costCounters.put(al.getCost(), new Counter(1));
            }
        }
    }

    private static String getTargetPath()
    {
        return String.format(Config.get("path.file.accessLog"), new Date());
    }

}
